home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
1320
/
1320.xpi
/
components
/
gmServiceGmail.js
< prev
next >
Wrap
Text File
|
2010-01-22
|
36KB
|
1,071 lines
// Gmail Manager
// By Todd Long <longfocus@gmail.com>
// http://www.longfocus.com/firefox/gmanager/
const GM_NOTIFY_STATE = "gmanager-accounts-notify-state";
function gmServiceGmail()
{
// Load the services
this._logger = Components.classes["@longfocus.com/gmanager/logger;1"].getService(Components.interfaces.gmILogger);
this._cookies = Components.classes["@longfocus.com/gmanager/cookies;1"].getService(Components.interfaces.gmICookies);
this._observer = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
if ("@mozilla.org/xre/app-info;1" in Components.classes)
{
var platformVersion = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo).platformVersion;
this._swapCookies = (platformVersion === null || platformVersion < "1.8.1");
}
}
gmServiceGmail.prototype = {
_email: null,
_password: null,
_isHosted: false,
_username: null,
_domain: null,
_loginURL: null,
_checkURL: null,
_status: Components.interfaces.gmIService.STATE_LOGGED_OUT,
_loggedIn: false,
_checking: false,
_inboxUnread: 0,
_savedDrafts: 0,
_spamUnread: 0,
_spaceUsed: null,
_percentUsed: null,
_totalSpace: null,
_labels: null,
_snippets: null,
_timer: null,
_connectionPhase: 0,
_channel: null,
_cookies: null,
_swapCookies: true,
_log: function(aMsg)
{
this._logger.log("(" + this.email + ") " + aMsg);
},
/**
* gmIServiceGmail
*/
get isHosted() { return this._isHosted; },
get username() { return this._username; },
get domain() { return this._domain; },
/**
* gmIService
*/
get email() { return this._email; },
get status() { return this._status; },
get loggedIn() { return this._loggedIn; },
get checking() { return this._checking; },
get inboxUnread() { return this._inboxUnread; },
get savedDrafts() { return this._savedDrafts; },
get spamUnread() { return this._spamUnread; },
get spaceUsed() { return this._spaceUsed; },
get percentUsed() { return this._percentUsed; },
get totalSpace() { return this._totalSpace; },
getInbox: function(aPassword)
{
return this._getService(aPassword);
},
getCompose: function(aPassword, aHref)
{
var href = (aHref ? aHref.replace("mailto:", "&to=").replace("subject=", "su=").replace(/ /g, "%20").replace("?", "&") : "");
return this._getService(aPassword, "view=cm&fs=1" + href);
},
_getService: function(aPassword, /* Optional */ aContinueData)
{
var serviceURI = new Object();
if (aContinueData == undefined)
aContinueData = "";
serviceURI.url = this._loginURL;
serviceURI.data = "ltmpl=default<mplcache=2" +
"&continue=" + encodeURIComponent(this._checkURL + aContinueData) +
"&service=mail&rm=false<mpl=default" +
"&Email=" + encodeURIComponent(this.isHosted ? this.username : this.email) +
"&Passwd=" + encodeURIComponent(aPassword) +
"&rmShown=1&signIn=Sign+in";
var xmlHttpRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
// Send the HTTP request
xmlHttpRequest.open("GET", this._loginURL, false);
xmlHttpRequest.send(null);
if (xmlHttpRequest.status == 200)
{
var httpChannel = xmlHttpRequest.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
serviceURI.cookies = this._cookieMonster(httpChannel);
}
if (serviceURI.cookies && "GALX" in serviceURI.cookies)
serviceURI.data += "&" + serviceURI.cookies["GALX"].pair;
return serviceURI;
},
getLabels: function(aCount)
{
var labels = new Array();
if (this._labels)
labels = this._labels;
aCount.value = labels.length;
return labels;
},
getSnippets: function(aCount)
{
var snippets = new Array();
if (this._snippets)
snippets = this._snippets;
aCount.value = snippets.length;
return snippets;
},
_fixGmailOffline: function()
{
// Fix for Gmail Offline (i.e. cookie management)
var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"].getService(Components.interfaces.nsICookieManager2);
var cookieEnumerator = cookieManager.enumerator;
var cookieName = (this.isHosted ? "GAUSR@" + this.domain : "GAUSR");
var cookieHost = "mail.google.com";
var gausrExists = false;
while (cookieEnumerator.hasMoreElements() && !gausrExists)
{
var cookie = cookieEnumerator.getNext();
if (cookie instanceof Components.interfaces.nsICookie)
gausrExists = (cookie.host == cookieHost && cookie.name == cookieName);
}
if (!gausrExists)
{
var cookiePath = (this.isHosted ? "/a/" + this.domain : "/mail");
try {
cookieManager.add(cookieHost, cookiePath, cookieName, this.email, false, false, true, Math.pow(2, 34));
} catch(e) {
cookieManager.add(cookieHost, cookiePath, cookieName, this.email, false, true, Math.pow(2, 34));
}
}
},
init: function(aEmail)
{
this._email = aEmail.toLowerCase();
this._isHosted = (this.email.indexOf("@gmail.com") == -1 && this.email.indexOf("@googlemail.com") == -1);
this._username = this.email.split("@")[0];
this._domain = this.email.split("@")[1];
// Check if the email is hosted
if (this.isHosted)
{
this._loginURL = "https://www.google.com/a/" + this.domain + "/LoginAction2";
this._checkURL = "https://mail.google.com/a/" + this.domain + "/?";
}
else
{
this._loginURL = "https://www.google.com/accounts/ServiceLoginAuth";
this._checkURL = "https://mail.google.com/mail/?";
}
},
login: function(aPassword)
{
const emptyRegExp = /^\s*$/;
// Check if already logged in or checking
if (this.loggedIn || this.checking)
return;
// Check if the password is specified
if (aPassword === null || emptyRegExp.test(aPassword))
{
this._defaults();
this._setChecking(false);
this._setStatus(Components.interfaces.gmIService.STATE_ERROR_PASSWORD);
}
else
{
// Get the connection details
var serviceURI = this._getService(aPassword, "labs=0");
// Setup the cookies
this._cookies = serviceURI.cookies;
// Save the password in case of connection timeout
this._password = aPassword;
// Fix the cookies for Gmail Offline
this._fixGmailOffline();
// Set checking and send the server request
this._setChecking(true);
this._serverRequest(serviceURI.url, serviceURI.data);
}
},
logout: function()
{
this._defaults();
this._setChecking(false);
this._setStatus(Components.interfaces.gmIService.STATE_LOGGED_OUT);
},
check: function()
{
// Check if already checking
if (this.checking)
return;
// Set checking and send the server request
this._setChecking(true);
this._serverRequest(this._checkURL + "labs=0");
},
notify: function(aTimer)
{
// Check if already checking
if (this.checking)
this._setRetryError(Components.interfaces.gmIService.STATE_ERROR_TIMEOUT);
else
{
// Check if already logged in
if (this.loggedIn)
this.check();
else
this.login(this._password);
}
},
resetUnread: function()
{
// Reset the unread counts
this._inboxUnread = 0;
this._spamUnread = 0;
this._snippets = null;
if (this._labels)
{
for (var i = 0; i < this._labels.length; i++)
this._labels[i].unread = 0;
}
// Update the status so that any observers get notified
// and can update the account details appropriately
this._setStatus(this.status);
},
_setStatus: function(aStatus)
{
// Notify the observers with the status
this._status = aStatus;
this._observer.notifyObservers(null, GM_NOTIFY_STATE, this.email + "|" + this.status);
},
_setChecking: function(aChecking)
{
if (aChecking)
{
// Preserve request cookies (prior to Mozilla 1.8.1)
if (this._swapCookies)
{
// Load the Google cookies
this._cookies.loadSession("google.com");
}
try {
// Add the HTTP observers
this._observer.addObserver(this, "http-on-modify-request", false);
this._observer.addObserver(this, "http-on-examine-response", false);
} catch(e) {}
// Set the status connecting
this._setStatus(Components.interfaces.gmIService.STATE_CONNECTING);
// Reset the connection phase
this._connectionPhase = 0;
// Start the timeout timer (30 seconds)
this._startTimer(30000);
}
else
{
// Preserve request cookies (prior to Mozilla 1.8.1)
if (this._swapCookies)
{
// Restore the Google cookies
this._cookies.restoreSession("google.com");
}
try {
// Remove the HTTP observers
this._observer.removeObserver(this, "http-on-modify-request");
this._observer.removeObserver(this, "http-on-examine-response");
} catch(e) {}
// Stop the timeout timer
this._stopTimer();
}
// Set whether checking or not
this._checking = aChecking;
},
_startTimer: function(aInterval)
{
var interval = parseInt(aInterval);
// Stop the timeout timer
this._stopTimer();
// Check if the interval is valid
if (!isNaN(interval) && interval > 0)
{
// Start the timeout timer which fires only once
this._timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
this._timer.initWithCallback(this, interval, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
},
_stopTimer: function()
{
// Check if the timeout timer is active
if (this._timer)
{
// Kill the timeout timer
this._timer.cancel();
this._timer = null;
}
},
_setRetryError: function(aStatus)
{
this._setChecking(false);
this._setStatus(aStatus);
this._startTimer(30000);
},
_defaults: function()
{
// Account details
this._password = null;
this._loggedIn = false;
this._checking = false;
this._inboxUnread = 0;
this._savedDrafts = 0;
this._spamUnread = 0;
this._spaceUsed = null;
this._percentUsed = null;
this._totalSpace = null;
this._labels = null;
this._snippets = null;
// Login stuff
this._connectionPhase = 0;
this._channel = null;
this._cookies = null;
},
_serverRequest: function(aURL, /* Optional */ aData)
{
this._log("request URL = " + aURL);
var ioService = Components.classes["@mozilla.org/network/io-service;1"].createInstance(Components.interfaces.nsIIOService);
var uri = ioService.newURI(aURL, null, null);
// Create the HTTP channel to follow
this._channel = ioService.newChannelFromURI(uri);
// Check for any POST data
if (typeof aData === "string")
{
var stringInputStream = Components.classes["@mozilla.org/io/string-input-stream;1"].createInstance(Components.interfaces.nsIStringInputStream);
stringInputStream.setData(aData, aData.length);
var uploadChannel = this._channel.QueryInterface(Components.interfaces.nsIUploadChannel);
uploadChannel.setUploadStream(stringInputStream, "application/x-www-form-urlencoded", -1);
var httpChannel = this._channel.QueryInterface(Components.interfaces.nsIHttpChannel);
httpChannel.requestMethod = "POST";
}
// Create the observer for server response
var observer = new this.observer(this);
// Open the HTTP channel for server request
this._channel.notificationCallbacks = observer;
this._channel.asyncOpen(observer, null);
},
_cookieMonster: function(aHttpChannel, /* Optional */ aCookies)
{
var cookies = aCookies;
if (cookies == null)
cookies = new Object();
if (aHttpChannel != null)
{
try {
var cookieHeader = aHttpChannel.getResponseHeader("Set-Cookie");
String.prototype.firstMatch = function(aRegExp)
{
var firstMatch = null;
try {
var match = this.match(aRegExp);
firstMatch = (match && match.length >= 1 ? match[1] : null);
} catch(e) {
/* Regular expression format error */
}
return firstMatch;
}
var rawCookies = cookieHeader.split("\n");
for (var i = 0; i < rawCookies.length; i++)
{
var cookiePair = rawCookies[i].firstMatch(/^(.*?)(?=;|$)/);
var cookieName = cookiePair.firstMatch(/(.*?)=/);
var cookieValue = cookiePair.firstMatch(/=(.*)/);
var cookieDomain = rawCookies[i].firstMatch(/Domain=(.*?)(?=;|$)/);
var cookieExpires = rawCookies[i].firstMatch(/Expires=(.*?)(?=;|$)/);
var cookiePath = rawCookies[i].firstMatch(/Path=(.*?)(?=;|$)/);
if (cookieDomain === null)
cookieDomain = aHttpChannel.URI.host;
if (cookieExpires !== null)
cookieExpires = cookieExpires.replace(/-/g, " ");
this._log("cookiePair = " + cookiePair);
// Check if the cookie has expired
if (Date.parse(cookieExpires) < Date.now())
delete cookies[cookieName];
else
cookies[cookieName] = { pair: cookiePair, name: cookieName, value: cookieValue, domain: cookieDomain, path: cookiePath };
}
} catch(e) {}
}
return cookies;
},
observe: function(aSubject, aTopic, aData)
{
// Check if this is the channel being followed
if (aSubject == this._channel)
{
var httpChannel = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
if (this._cookies === null)
this._cookies = new Object();
switch (aTopic)
{
case "http-on-modify-request":
{
// Clears the cookies
httpChannel.setRequestHeader("Cookie", "", false);
for (var cookieName in this._cookies)
{
if (httpChannel.URI.host.indexOf(this._cookies[cookieName].domain) > -1)
httpChannel.setRequestHeader("Cookie", this._cookies[cookieName].pair, true);
}
break;
}
case "http-on-examine-response":
{
// Save the incoming cookies
this._cookies = this._cookieMonster(httpChannel, this._cookies);
// Clear the incoming cookies (Firefox 2.0, Mozilla 1.8.1)
httpChannel.setResponseHeader("Set-Cookie", "", false);
break;
}
}
}
},
callback: function(aData, aRequest)
{
// Get the http channel containing our data
var httpChannel = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel);
try {
const loginRegExp = /\/.*Login.*/i;
// Get HTTP response information
var status = httpChannel.responseStatus;
var path = httpChannel.URI.path;
this._log("connection phase = " + this._connectionPhase);
this._log("http URI path = " + path);
this._log("http response status = " + status)
this._log("data = " + aData);
if (status === null || status !== 200) // Bad status
{
// Server error, try again in 30 seconds
this._setRetryError(Components.interfaces.gmIService.STATE_ERROR_NETWORK);
}
else if (loginRegExp.test(path) && this._connectionPhase > 0) // Bad password
{
this._defaults();
this._setChecking(false);
this._setStatus(Components.interfaces.gmIService.STATE_ERROR_PASSWORD);
}
} catch(e) {
// Network error, try again in 30 seconds
this._setRetryError(Components.interfaces.gmIService.STATE_ERROR_NETWORK);
}
// Only continue if we're still checking!
if (this.checking)
{
const globalsRegExp = /var\s+GLOBALS\s*=/i;
const viewDataRegExp = /var\s+VIEW_DATA\s*=/i;
var isLatest = (globalsRegExp.test(aData) && viewDataRegExp.test(aData));
if (isLatest)
{
this._log("Using the latest Gmail version =)");
this._connectionPhase = 2;
}
else
this._log("Using the old Gmail version =(");
// Ok, everything looks good so far =)
switch (++this._connectionPhase)
{
case 1:
{
// Send the server request
this._serverRequest(this._checkURL + "ui=2");
break;
}
case 2:
{
// Send the server request
this._serverRequest(this._checkURL + "ui=1&view=tl&search=inbox&start=0&init=1");
break;
}
case 3:
{
// Try to get the account information...
try {
// Quota
var quMatches = JSON.fromString(aData.match(/\["qu",(.|\s)+?]/)[0]);
this._log("\"qu\" match was " + (quMatches ? "found" : "not found"));
this._spaceUsed = quMatches[1];
this._totalSpace = quMatches[2];
this._percentUsed = quMatches[3];
this._log("space used = " + this.spaceUsed);
this._log("total space = " + this.totalSpace);
this._log("percent used = " + this.percentUsed);
} catch(e) {
this._log("error getting the quota: " + e);
}
try {
// Initialize the labels
this._labels = new Array();
// Check which Gmail version
if (isLatest)
{
// Inbox/Drafts/Spam/Labels
var ldMatches = JSON.fromString(aData.match(/\["ld",(.|\s)+?(\[|])(\s*]){2}/)[0]);
this._log("\"ld\" match was " + (ldMatches ? "found" : "not found"));
with (Math) {
this._inboxUnread = max(0, ldMatches[1][0][1]);
this._savedDrafts = max(0, ldMatches[1][4][1]);
this._spamUnread = max(0, ldMatches[1][6][1]);
}
for (var i = 0; i < ldMatches[2].length; i++)
this._labels.push({
"name" : ldMatches[2][i][0],
"unread" : ldMatches[2][i][1],
"total" : ldMatches[2][i][2]
});
}
else
{
// Inbox/Drafts/Spam
var dsMatches = JSON.fromString(aData.match(/\["ds",(.|\s)+?](\s*]){2}/)[0]);
this._log("\"ds\" match was " + (dsMatches ? "found" : "not found"));
with (Math) {
this._inboxUnread = max(0, dsMatches[1][0][1]);
this._savedDrafts = max(0, dsMatches[1][1][1]);
this._spamUnread = max(0, dsMatches[1][2][1]);
}
// Labels
var ctMatches = JSON.fromString(aData.match(/\["ct",(.|\s)+?](\s*]){2}/)[0]);
this._log("\"ct\" match was " + (ctMatches ? "found" : "not found"));
for (var i = 0; i < ctMatches[1].length; i++)
this._labels.push({
"name" : ctMatches[1][i][0],
"unread" : ctMatches[1][i][1],
"total" : -1
});
}
this._log("inboxUnread = " + this.inboxUnread);
this._log("savedDrafts = " + this.savedDrafts);
this._log("spamUnread = " + this.spamUnread);
if (this._labels.length > 0)
{
for (var i = 0; i < this._labels.length; i++)
{
var label = this._labels[i];
var hasTotal = (label.total > -1);
this._log(label.name + " (" + label.unread + (hasTotal ? " of " + label.total : "") + ")");
}
}
else
this._log("no labels were found");
} catch(e) {
this._log("error getting the inbox/drafts/spam/labels: " + e);
}
try {
// Initialize the snippets
this._snippets = new Array();
// Check which Gmail version
if (isLatest)
{
// Snippets
var tbMatches = aData.match(/\["tb",(.|\s)+?]\s*]/g);
this._log("\"tb\" match was " + (tbMatches ? "found" : "not found"));
if (tbMatches === null)
tbMatches = new Array();
for (var i = 0; i < tbMatches.length; i++)
{
var snippets = JSON.fromString(tbMatches[i]);
var offset = snippets[1];
var size = snippets[2];
for (var j = 3; j < (size + 3); j++)
{
// Check if the snippet is unread
if (snippets[j][3] == 0)
{
this._snippets.push({
"id" : snippets[j][0],
"unread" : true,
"from" : this._replaceHtmlCodes(this._stripHtml(snippets[j][7])),
"subject" : this._replaceHtmlCodes(this._stripHtml(snippets[j][9])),
"msg" : this._replaceHtmlCodes(this._stripHtml(snippets[j][10])),
"time" : this._replaceHtmlCodes(this._stripHtml(snippets[j][14])),
"date" : snippets[j][15]
});
}
}
}
}
else
{
// Snippets
var tMatches = aData.match(/\["t",(.|\s)+?]\s*]/g);
this._log("\"t\" match was " + (tMatches ? "found" : "not found"));
if (tMatches === null)
tMatches = new Array();
for (var i = 0; i < tMatches.length; i++)
{
var snippets = JSON.fromString(tMatches[i]);
for (var j = 1; j < snippets.length; j++)
{
// Check if the snippet is unread
if (snippets[j][1] == 1)
{
this._snippets.push({
"id" : snippets[j][0],
"unread" : true,
"from" : this._replaceHtmlCodes(this._stripHtml(snippets[j][4])),
"subject" : this._replaceHtmlCodes(this._stripHtml(snippets[j][6])),
"msg" : this._replaceHtmlCodes(this._stripHtml(snippets[j][7])),
"time" : this._replaceHtmlCodes(this._stripHtml(snippets[j][3])),
"date" : snippets[j][12]
});
}
}
}
}
if (this._snippets.length > 0)
{
this._log(this._snippets.length + " snippet(s) were found");
for (var i = 0; i < this._snippets.length; i++)
{
var snippet = this._snippets[i];
this._log("snippet[" + i + "].id = " + snippet.id);
this._log("snippet[" + i + "].unread = " + snippet.unread);
this._log("snippet[" + i + "].from = " + snippet.from);
this._log("snippet[" + i + "].subject = " + snippet.subject);
this._log("snippet[" + i + "].msg = " + snippet.msg);
this._log("snippet[" + i + "].time = " + snippet.time);
this._log("snippet[" + i + "].date = " + snippet.date);
}
}
else
this._log("no snippets were found");
} catch(e) {
this._log("error getting the snippets: " + e);
}
this._loggedIn = true;
this._setChecking(false);
this._setStatus(Components.interfaces.gmIService.STATE_LOGGED_IN);
break;
}
}
}
},
_stripHtml: function(aData)
{
return aData.replace(/(<([^>]+)>)/ig, "");
},
_replaceHtmlCodes: function(aData)
{
var htmlCodes = new Array(
[">", ">"], ["<", "<"], ["'", "'"], [""", "\""],
["&", "&"], ["˜", "~"], ["™", "?"], ["©", "?"],
["®", "?"], ["…", ""] );
for (var i = 0; i < htmlCodes.length; i++)
{
var regExp = new RegExp(htmlCodes[i][0], "g");
aData = aData.replace(regExp, htmlCodes[i][1]);
}
return aData;
},
observer: function(aThis)
{
return ({
_data: "",
/**
* nsIStreamListener
*/
onStartRequest: function(aRequest, aContext) {
this._data = "";
},
onStopRequest: function(aRequest, aContext, aStatus) {
aThis.callback(this._data, aRequest);
},
onDataAvailable: function(aRequest, aContext, aStream, aSourceOffset, aLength) {
var scriptableInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableInputStream.init(aStream);
this._data += scriptableInputStream.read(aLength);
},
/**
* nsIChannelEventSink
*/
onChannelRedirect: function (aOldChannel, aNewChannel, aFlags) {
aThis._channel = aNewChannel;
},
/**
* nsIProgressEventSink
*/
onProgress: function (aRequest, aContext, aProgress, aProgressMax) { /* Stub */ },
onStatus: function (aRequest, aContext, aStatus, aStatusArg) { /* Stub */ },
/**
* nsIHttpEventSink
*/
onRedirect: function (aOldChannel, aNewChannel) { /* Stub */ },
/**
* nsIInterfaceRequestor
*/
getInterface: function(aIID) {
try {
return this.QueryInterface(aIID);
} catch(e) {
throw Components.results.NS_NOINTERFACE;
}
},
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIStreamListener) ||
aIID.equals(Components.interfaces.nsIChannelEventSink) ||
aIID.equals(Components.interfaces.nsIProgressEventSink) ||
aIID.equals(Components.interfaces.nsIHttpEventSink) ||
aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
});
},
QueryInterface: function(iid)
{
if (iid.equals(Components.interfaces.gmIServiceGmail) ||
iid.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
}
var myModule = {
firstTime: true,
myCID: Components.ID("{b07df9d0-f7dd-11da-974d-0800200c9a66}"),
myDesc: "Gmail Account Service",
myProgID: "@longfocus.com/gmanager/service/gmail;1",
myFactory: {
createInstance: function (outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new gmServiceGmail()).QueryInterface(iid);
}
},
registerSelf: function (compMgr, fileSpec, location, type)
{
if (this.firstTime) {
this.firstTime = false;
throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
}
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this.myCID, this.myDesc, this.myProgID, fileSpec, location, type);
},
getClassObject: function (compMgr, cid, iid)
{
if (!cid.equals(this.myCID))
throw Components.results.NS_ERROR_NO_INTERFACE;
if (!iid.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
return this.myFactory;
},
canUnload: function(compMgr) { return true; }
};
function NSGetModule(compMgr, fileSpec) { return myModule; }
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla code.
*
* The Initial Developer of the Original Code is
* Simon B├╝nzli <zeniko@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Utilities for JavaScript code to handle JSON content.
* See http://www.json.org/ for comprehensive information about JSON.
*
* Import this module through
*
* Components.utils.import("resource://gre/modules/JSON.jsm");
*
* Usage:
*
* var newJSONString = JSON.toString( GIVEN_JAVASCRIPT_OBJECT );
* var newJavaScriptObject = JSON.fromString( GIVEN_JSON_STRING );
*
* Note: For your own safety, Objects/Arrays returned by
* JSON.fromString aren't instanceof Object/Array.
*/
var EXPORTED_SYMBOLS = ["JSON"];
// The following code is a loose adaption of Douglas Crockford's code
// from http://www.json.org/json.js (public domain'd)
// Notable differences:
// * Unserializable values such as |undefined| or functions aren't
// silently dropped but always lead to a TypeError.
// * An optional key blacklist has been added to JSON.toString
var JSON = {
/**
* Converts a JavaScript object into a JSON string.
*
* @param aJSObject is the object to be converted
* @param aKeysToDrop is an optional array of keys which will be
* ignored in all objects during the serialization
* @return the object's JSON representation
*
* Note: aJSObject MUST not contain cyclic references.
*/
toString: function JSON_toString(aJSObject, aKeysToDrop) {
// we use a single string builder for efficiency reasons
var pieces = [];
// this recursive function walks through all objects and appends their
// JSON representation (in one or several pieces) to the string builder
function append_piece(aObj) {
if (typeof aObj == "string") {
aObj = aObj.replace(/[\\"\x00-\x1F\u0080-\uFFFF]/g, function($0) {
// use the special escape notation if one exists, otherwise
// produce a general unicode escape sequence
switch ($0) {
case "\b": return "\\b";
case "\t": return "\\t";
case "\n": return "\\n";
case "\f": return "\\f";
case "\r": return "\\r";
case '"': return '\\"';
case "\\": return "\\\\";
}
return "\\u" + ("0000" + $0.charCodeAt(0).toString(16)).slice(-4);
});
pieces.push('"' + aObj + '"')
}
else if (typeof aObj == "boolean") {
pieces.push(aObj ? "true" : "false");
}
else if (typeof aObj == "number" && isFinite(aObj)) {
// there is no representation for infinite numbers or for NaN!
pieces.push(aObj.toString());
}
else if (aObj === null) {
pieces.push("null");
}
// if it looks like an array, treat it as such - this is required
// for all arrays from either outside this module or a sandbox
else if (aObj instanceof Array ||
typeof aObj == "object" && "length" in aObj &&
(aObj.length === 0 || aObj[aObj.length - 1] !== undefined)) {
pieces.push("[");
for (var i = 0; i < aObj.length; i++) {
arguments.callee(aObj[i]);
pieces.push(",");
}
if (aObj.length > 0)
pieces.pop(); // drop the trailing colon
pieces.push("]");
}
else if (typeof aObj == "object") {
pieces.push("{");
for (var key in aObj) {
// allow callers to pass objects containing private data which
// they don't want the JSON string to contain (so they don't
// have to manually pre-process the object)
if (aKeysToDrop && aKeysToDrop.indexOf(key) != -1)
continue;
arguments.callee(key.toString());
pieces.push(":");
arguments.callee(aObj[key]);
pieces.push(",");
}
if (pieces[pieces.length - 1] == ",")
pieces.pop(); // drop the trailing colon
pieces.push("}");
}
else {
throw new TypeError("No JSON representation for this object!");
}
}
append_piece(aJSObject);
return pieces.join("");
},
/**
* Converts a JSON string into a JavaScript object.
*
* @param aJSONString is the string to be converted
* @return a JavaScript object for the given JSON representation
*/
fromString: function JSON_fromString(aJSONString) {
if (!this.isMostlyHarmless(aJSONString))
throw new SyntaxError("No valid JSON string!");
var s = new Components.utils.Sandbox("about:blank");
return Components.utils.evalInSandbox("(" + aJSONString + ")", s);
},
/**
* Checks whether the given string contains potentially harmful
* content which might be executed during its evaluation
* (no parser, thus not 100% safe! Best to use a Sandbox for evaluation)
*
* @param aString is the string to be tested
* @return a boolean
*/
isMostlyHarmless: function JSON_isMostlyHarmless(aString) {
const maybeHarmful = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/;
const jsonStrings = /"(\\.|[^"\\\n\r])*"/g;
return !maybeHarmful.test(aString.replace(jsonStrings, ""));
}
};